/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System System Specific Module                         */
/*                                                                           */
/*    Copyright (C) 2010 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    netUDP - User Datagram Protocol implementation                         */
/*****************************************************************************/

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/
#include <netPacketBuffer.h>
#include <netUDP.h>
#include <netMAC.h>

/*****************************************************************************/
/* Constants                                                                 */
/*****************************************************************************/
#define netUDP_HEADER_LENGTH 8

/*****************************************************************************/
/* Functions                                                                 */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Processes ICMP packet
//! \param IP header information of ICMP packet
void netUDPProcess( netIPHeaderInfo* in_header_info)
{
  dosWord pos;
  dosDWord word_sum;
  dosWord checksum;
  dosWord source_port;
  dosWord destination_port;
  dosWord length;
  netSOCKET s;
  netPacketInfo packet_info;

  // sanity check
  if( in_header_info->Protocol != netIPDT_UDP )
  {
    netDeleteRXPacket();
    return;
  }
  
  // check UDP header
  pos = netGetRXPacketPos();
  
  netReadWordRXPacket( &source_port );
  netReadWordRXPacket( &destination_port );
  netReadWordRXPacket( &length );
  netReadWordRXPacket( &checksum );

  // check CRC (if enabled)
  if( checksum != 0 )
  {
    word_sum = 0;
    netSetRXPacketPos( pos );
    netUpdateRXPacketChecksum( in_header_info->PacketLength - in_header_info->HeaderLength, &word_sum );
    checksum = netUDPCalculateChecksum( in_header_info, word_sum );
  
    if( checksum != 0 )
    {
      netDeleteRXPacket();
      return;
    }
  }

  // packet valid -> check if there is opened socket
  s = netFindSocket( SOCK_DGRAM, destination_port );

  if( s == INVALID_SOCKET )
  {
    netDeleteRXPacket();
    return;
  }

  // socket found -> remove from the RX queue and insert into the socket queue
  netSetRXPacketPos( pos  + netUDP_HEADER_LENGTH );
  packet_info.Address.Val = in_header_info->Address.Val;
  packet_info.PayloadLength = in_header_info->PacketLength - in_header_info->HeaderLength - netUDP_HEADER_LENGTH;
  packet_info.RemotePort = source_port;
  netMoveRXPacketToSocketBuffer( s, &packet_info );
}

///////////////////////////////////////////////////////////////////////////////
//! Calculates ICMP checksum from sum of words
//! \param IP header info (used for pseudo header)
//! \param Sum of words
//! \return ICMP checksum
dosWord netUDPCalculateChecksum( netIPHeaderInfo* in_header_info, dosDWord in_checksum )
{
  // add pseudo header
  in_checksum += ((dosWord)netIPDT_UDP) + (in_header_info->PacketLength - in_header_info->HeaderLength);
  in_checksum += ((dosWord)in_header_info->Address.v[0] << 8) + in_header_info->Address.v[1];
  in_checksum += ((dosWord)in_header_info->Address.v[2] << 8) + in_header_info->Address.v[3];
  in_checksum += ((dosWord)g_netIPAddress.v[0] << 8) + g_netIPAddress.v[1];
  in_checksum += ((dosWord)g_netIPAddress.v[2] << 8) + g_netIPAddress.v[3];
   
  // now calculate the sum over the bytes in the sum
  // until the result is only 16bit long
  while( (in_checksum>>16) != 0 )
  {
    in_checksum = (in_checksum & 0xFFFF) + (in_checksum >> 16);
  }
  
  // one's complement
  if( in_checksum == 0 )
     return 0;
  else
    return ~((dosWord)in_checksum);
}

///////////////////////////////////////////////////////////////////////////////
//! Creates an empty UDP header
//! \param Source port
//! \param Destination port
//! \param Packet payload length
dosBool netCreateUDPHeader( dosWord in_source_port, dosWord in_destination_port, dosWord in_length )
{
  if( !netWriteWordTXPacket( in_source_port ) )
    return dosFalse;

  if( !netWriteWordTXPacket( in_destination_port ) )
    return dosFalse;

  if( !netWriteWordTXPacket( in_length + netUDP_HEADER_LENGTH ) )  // Length
    return dosFalse;

  if( !netWriteWordTXPacket( 0 ) )  // checksum
    return dosFalse;

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Updates UDP header (length and checksum)
//! \param IP Header info (for checksum calculation)
//! \param Packet payload length
void netUpdateUDPHeader( netIPHeaderInfo* in_header_info, dosWord in_payload_length )
{
  dosWord pos;
  dosDWord word_sum;
  dosWord checksum;

	// packet length
	in_header_info->PacketLength = netIP_HEADER_LENGTH + netUDP_HEADER_LENGTH + in_payload_length;

  // store header position
  pos = netGetTXPacketPos();

	// caculate checksum
  word_sum = 0;
  netUpdateTXPacketChecksum( in_payload_length + netUDP_HEADER_LENGTH, &word_sum );
  checksum = netUDPCalculateChecksum( in_header_info, word_sum );

	// update UDP header
  netSetTXPacketPos(pos+4);
  netWriteWordTXPacket(in_payload_length + netUDP_HEADER_LENGTH
		);
  netWriteWordTXPacket(checksum);
	netIPUpdateHeader(in_header_info);
}
